﻿* TODO: Review the working-storage sections.

       class-id. MainWindow as "Barbarian.MainWindow" is partial 
	             inherits type "System.Windows.Window".			
				 
       environment division.
       configuration section.
       repository.
           class AboutWindow               as "Barbarian.AboutWindow"
           class ParagraphWriter           as "Barbarian.ParagraphWriter"
           class Resources                 as "Barbarian.Properties.Resources"
           class Snippet                   as "Barbarian.Snippet"
           class SnippetOptions            as "Barbarian.SnippetOptions"
           class OpenFileDialog            as "Microsoft.Win32.OpenFileDialog"
           class SaveFileDialog            as "Microsoft.Win32.SaveFileDialog"
           class ArrayOfByte               as "System.Byte[]"
           class ArrayOfObject             as "System.Object[]"
           class ArrayOfString             as "System.String[]"
           class CompilerError             as "System.CodeDom.Compiler.CompilerError"
           class CompilerErrorCollection   as "System.CodeDom.Compiler.CompilerErrorCollection"
           class CompilerResults           as "System.CodeDom.Compiler.CompilerResults"
           class Directory                 as "System.IO.Directory"
           enum  FileAccess                as "System.IO.FileAccess"
           enum  FileMode                  as "System.IO.FileMode"
           enum  FileShare                 as "System.IO.FileShare"
           class FileStream                as "System.IO.FileStream"
           class Path                      as "System.IO.Path"
           class MethodInfo                as "System.Reflection.MethodInfo"
           class TargetInvocationException as "System.Reflection.TargetInvocationException"
           class UTF8Encoding              as "System.Text.UTF8Encoding"
           class ComboBoxItem              as "System.Windows.Controls.ComboBoxItem"
           class MenuItem                  as "System.Windows.Controls.MenuItem"
           class DocRun                    as "System.Windows.Documents.Run"
           class LineBreak                 as "System.Windows.Documents.LineBreak"
           class Brushes                   as "System.Windows.Media.Brushes"
           enum  Visibility                as "System.Windows.Visibility"
           enum  WindowState               as "System.Windows.WindowState"
           class SystemInformation         as "System.Windows.Forms.SystemInformation"
           .

       object.
       working-storage section.
       01 _activeLanguage string value null.
       01 _currentOptions type "System.Windows.Controls.MenuItem".
       01 _filename1      string value null.
       01 _filename2      string value null.
       01 _newline        string value type "System.Environment"::"NewLine".
       
       procedure division.

       method-id. get property "CurrentOptions" is public.
       procedure division returning options as SnippetOptions.
           set options to _currentOptions::"Tag" as SnippetOptions
       end method.

       method-id. get property "DefaultExt" is private.
       procedure division returning result as string.
           if self::"_activeLanguage" = "C#"
               set result to "scs"
           end-if
           if self::"_activeLanguage" = "C++"
               set result to "scpp"
           end-if
           if self::"_activeLanguage" = "VB"
               set result to "svb"
           end-if
       end method.

       method-id. get property "DefaultExportExt" is private.
       procedure division returning result as string.
           if self::"_activeLanguage" = "C#"
               set result to "cs"
           end-if
           if self::"_activeLanguage" = "C++"
               set result to "cpp"
           end-if
           if self::"_activeLanguage" = "VB"
               set result to "vb"
           end-if
       end method.

       method-id. get property "Filter" is private.
       procedure division returning result as string.
           if self::"_activeLanguage" = "C#"
               set result to "C# Snippets (*.scs)|*.scs|All files (*.*)|*.*"
           end-if
           if self::"_activeLanguage" = "C++"
               set result to "C++ Snippets (*.scpp)|*.scs|All files (*.*)|*.*"
           end-if
           if self::"_activeLanguage" = "VB"
               set result to "VB Snippets (*.svb)|*.svb|All files (*.*)|*.*"
           end-if
       end method.

       method-id. get property "ExportFilter" is private.
       procedure division returning result as string.
           if self::"_activeLanguage" = "C#"
               set result to "C# Files (*.cs)|*.cs|All files (*.*)|*.*"
           end-if
           if self::"_activeLanguage" = "C++"
               set result to "C++ Files (*.cpp)|*.cs|All files (*.*)|*.*"
           end-if
           if self::"_activeLanguage" = "VB"
               set result to "VB Snippets (*.vb)|*.vb|All files (*.*)|*.*"
           end-if
       end method.

       method-id. NEW.
       working-storage section.
       01  languageItem ComboBoxItem.
       procedure division.
           invoke self::"InitializeComponent"
           invoke self::"LoadSnippetOptions"()
           invoke self::"codeBox"::"Focus"()
           set languageItem to languageComboBox::"SelectedItem" as ComboBoxItem
           set self::"_activeLanguage" to languageItem::"Content"::"ToString"()
       end method NEW.

       method-id. LoadSnippetOptions private.
       working-storage section.
       01  options  type "System.Collections.Generic.List"[SnippetOptions].
       01  item     MenuItem.
       01  i        binary-long.
       procedure division.
       
           set options to SnippetOptions::"LoadAvailableOptions"() 
                       as type "System.Collections.Generic.List"[SnippetOptions]

           set i to 0
           perform with test before until i = options::"Count"

               set item to new MenuItem
               set item::"Header" to options::"Item"(i)::"Name"
               set item::"Tag" to options::"Item"(i)
               invoke item::"add_Checked"(new "System.Windows.RoutedEventHandler"(self::"HandleOptionsSelection"))
               set item::"IsCheckable" to true
               set item::"IsChecked" to (i = 0)

               invoke OptionsMenu::"Items"::"Add"(item)
               add 1 to i

           end-perform

       end method LoadSnippetOptions.

       method-id. HandleOptionsSelection final private.
       working-storage section.
       01  item  MenuItem.
       procedure division using by value sender as object e as type "System.Windows.RoutedEventArgs".

*          Here's an example of explicit conversion from one type to another,
*          using casting syntax (object-name as type-name):

           set item to e::"Source" as MenuItem

           if not (_currentOptions = null or item = _currentOptions) then
               set _currentOptions::"IsChecked" to false
           end-if
           
           set _currentOptions to item
           
       end method HandleOptionsSelection.
      
       method-id. FileNew final private.
       procedure division using by value sender as object, e as type "System.Windows.RoutedEventArgs".
           if self::"goButton1"::"IsEnabled"
               set self::"_filename1" to null
               invoke codeBox::"Clear"()
           else
               set self::"_filename2" to null
               invoke codeBox2::"Clear"()
           end-if
           invoke outputParagraph::"Inlines"::"Clear"()
       end method FileNew.

       method-id. FileOpen final private.
       working-storage section.
       01  dialog       OpenFileDialog.
       01  result       type "System.Nullable"[condition-value].
       procedure division using by value sender as object, e as type "System.Windows.Input.ExecutedRoutedEventArgs".
           set dialog to new OpenFileDialog
           set dialog::"Multiselect" to false
           set dialog::"DefaultExt" to self::"DefaultExt"
           set dialog::"Filter" to self::"Filter"

           set result to dialog::"ShowDialog"()

           if result::"HasValue" and result::"Value" then
               set codeBox::"Text" to type "System.IO.File"::"ReadAllText"(dialog::"FileName")
               if self::"goButton1"::"IsEnabled"
                   set self::"_filename1" to dialog::"FileName"
               else
                   set self::"_filename2" to dialog::"FileName"
               end-if
           end-if
       end method FileOpen.
       
       method-id. FileSave final private.
       working-storage section.
       01  snip  Snippet.
       procedure division using by value sender as object, e as type "System.Windows.Input.ExecutedRoutedEventArgs".
           if self::"goButton1"::"IsEnabled"
               if self::"_filename1" = null then
                   invoke self::"FileSaveAs"(sender, e)
                   goback
               end-if
               set snip to Snippet::"CreateNew"(self::"_activeLanguage", codeBox::"Text", self::"CurrentOptions")
               invoke snip::"Save"(_filename1)
           else
               if self::"_filename2" = null then
                   invoke self::"FileSaveAs"(sender, e)
                   goback
               end-if
               set snip to Snippet::"CreateNew"(self::"_activeLanguage", codeBox2::"Text", self::"CurrentOptions")
               invoke snip::"Save"(_filename2)
           end-if
       end method FileSave.
       
       method-id. FileSaveAs final private.
       working-storage section.
       01  dialog  SaveFileDialog.
       01  result  type "System.Nullable"[condition-value].
       procedure division using by value sender as object e as type "System.Windows.Input.ExecutedRoutedEventArgs".
           set dialog to new SaveFileDialog
           set dialog::"AddExtension" to true
           set dialog::"DefaultExt" to self::"DefaultExt"
           set dialog::"Filter" to self::"Filter"

           set result to dialog::"ShowDialog"()

           if result::"HasValue" and result::"Value" then
               if self::"goButton1"::"IsEnabled"
                   set self::"_filename1" to dialog::"FileName" 
               else
                   set self::"_filename2" to dialog::"FileName" 
               end-if
               invoke self::"FileSave"(sender, e)
           end-if
       end method FileSaveAs.
       
       method-id. CompileAndRun final private.
       working-storage section.
       01  snip          Snippet.
       01  results       CompilerResults.
       01  errors        CompilerErrorCollection.
       01  err           CompilerError.
       01  r             DocRun.
       01  i             binary-long value 0.
       01  snippetType   type "System.Type".
       01  invocationEx  TargetInvocationException.
       01  member        MethodInfo.
       01  writer        ParagraphWriter value ParagraphWriter::"CreateNew"(outputParagraph).
       01  kludge        type "System.IO.TextWriter" value new type "System.IO.StringWriter".
       01  ex            type "System.Exception".
       01  newline       string value type "System.Environment"::"NewLine".
       procedure division using by value sender as object e as type "System.Windows.RoutedEventArgs".

           invoke outputParagraph::"Inlines"::"Clear"()

           if self::"goButton1"::"IsEnabled"
               set snip to Snippet::"CreateNew"(self::"_activeLanguage", codeBox::"Text", self::"CurrentOptions")
           else
               set snip to Snippet::"CreateNew"(self::"_activeLanguage", codeBox2::"Text", self::"CurrentOptions")
           end-if
           
           if self::"_activeLanguage" = "C++/CLI: pure"
*              Use command-line methods. Can't compile from a CodeDomProvider.           
* Todo: Implement support for selected Options. For now we just do a simple default compile.
               invoke self::"CompileAndRunFromFile"(snip)
               goback
           end-if
           
           try
               set results to snip::"Compile"()
           catch ex
               set r to new DocRun(string::"Concat"("An exception occurred inside the Barbarian: ", newline,
                   ex::"Message", newline, "(Build failed)"))
               set r::"Foreground" to Brushes::"Red"
               invoke outputParagraph::"Inlines"::"Add"(r)
               goback
           end-try
           
           set errors to results::"Errors"
           
           perform with test before until i = errors::"Count"
           
               set err to errors::"Item"(i)
               set r to new DocRun(string::"Concat"(err::"Line", ": ", err::"ErrorText"))

               if err::"IsWarning" then
                   set r::"Foreground" to Brushes::"MediumBlue"
               else
                   set r::"Foreground" to Brushes::"Red"
               end-if
               
               invoke outputParagraph::"Inlines"::"Add"(r)
               invoke outputParagraph::"Inlines"::"Add"(new LineBreak)
               
               add 1 to i

           end-perform
           
           if results::"Errors"::"HasErrors"

               set r to new DocRun("(Build failed)")
               set r::"Foreground" to Brushes::"Red"
               invoke outputParagraph::"Inlines"::"Add"(r)
               goback

           else

               set r to new DocRun("(Build successful)")
               set r::"Foreground" to Brushes::"MediumBlue"
               invoke outputParagraph::"Inlines"::"Add"(r)
               invoke outputParagraph::"Inlines"::"Add"(new LineBreak)

* TODO: We should be able to set the console's output directly to a ParagraphWriter.
*       However, the console doesn't appear to invoke any of ParagraphWriter's methods.
*       As a workaround, we'll set the console's output to a StringWriter. When the snippet
*       is done executing, we'll call ParagraphWriter::"Write"(StringWriter::"ToString").
*       The problem with this, is that long-running snippets may appear non-responsive.

*               invoke type "System.Console"::"SetOut"(writer)
               invoke type "System.Console"::"SetOut"(kludge)
                              
               try
* TODO: The original C# code here in Snippy C# was:
*       snippetType.GetMethod("Main").Invoke(null, new object[] { new string[] { } });
*       When time allows, figure out the array initialization syntax, and send parameters.
                   set snippetType to results::"CompiledAssembly"::"GetType"("Snippet")
                   set member to snippetType::"GetMethod"("Main")
                   invoke member::"Invoke"(null)
               catch invocationEx
                   set r to new DocRun(string::"Format"("Exception: {0}", invocationEx::"InnerException"))
                   set r::"Foreground" to Brushes::"Red"
                   invoke outputParagraph::"Inlines"::"Add"(r)
               end-try

               invoke writer::"Write"(kludge::"ToString"())

           end-if

       end method CompileAndRun.
       
       method-id. CompileAndRunFromFile.
       working-storage section.
       01  sourceCode  string.
       01  includeLineNumbers  condition-value  value "True".
       01  src string.
       01  bat string.
       01  exe string.
       procedure division using snip as Snippet.
           set sourceCode to snip::"GenerateCode"(includeLineNumbers)
           set src to self::"WriteSourcefile"(sourceCode)
           set bat to self::"WriteBatchFile"(src)
           set exe to self::"RunBatchFile"(bat)
           invoke self::"RunExe"(exe)
           invoke self::"DeleteTempFiles"(src)
       end method CompileAndRunFromFile.

       method-id.  WriteSourcefile.
       working-storage section.
       01  tempFile  string.
       01  fs        FileStream.
       01  info      ArrayOfByte.
       procedure division using sourceCode as string returning srcFile as string.
           set tempFile to Path::"GetRandomFileName"()
           set srcFile to string::"Concat"(Path::"GetFileNameWithoutExtension"(tempFile), ".cpp")
* Todo: Add try-catch-finally around use of fs.            
           set fs to type "System.IO.File"::"Open"(srcFile, FileMode::"OpenOrCreate",
               FileAccess::"Write", FileShare::"None")
           set info to new UTF8Encoding(True)::"GetBytes"(sourceCode)
           invoke fs::"Write"(info, 0, info::"Length")
           invoke fs::"Close"()
       end method WriteSourceFile.

       method-id.  WriteBatchFile.
       working-storage section.
* Todo: Avoid hard-coding this path.      
       01  vsvars32  string  value "c:\Program Files\Microsoft Visual Studio 9.0\Common7\Tools\vsvars32.bat".
       01  command   string.
       01  fs        FileStream.
       01  info      ArrayOfByte.
       procedure division using srcFile as string returning batFile as string.
           set batFile to string::"Concat"(Path::"GetFileNameWithoutExtension"(srcFile), ".bat")
           invoke type "System.IO.File"::"Copy"(vsvars32, batFile, True)

*          Append a command to compile the source file.
           set command to string::"Concat"(_newline, "cl /nologo /clr ", srcFile)
* Todo: Add try-catch-finally around use of fs.            
           set fs to type "System.IO.File"::"Open"(batFile, FileMode::"Append",
               FileAccess::"Write", FileShare::"None")
           set info to new UTF8Encoding(True)::"GetBytes"(command)
           invoke fs::"Write"(info, 0, info::"Length")
           invoke fs::"Close"()
       end method WriteBatchFile.

       method-id.  RunBatchFile.
       working-storage section.
       01  p  type "System.Diagnostics.Process".
       01  r  DocRun.
       procedure division using batFile as string returning exeFile as string.
           set exeFile to string::"Concat"(Path::"GetFileNameWithoutExtension"(batFile), ".exe")

           set p to new type "System.Diagnostics.Process"
           set p::"StartInfo"::"UseShellExecute" to false
           set p::"StartInfo"::"RedirectStandardOutput" to true
           set p::"StartInfo"::"RedirectStandardError" to true
           set p::"StartInfo"::"FileName" to batFile
           invoke p::"Start"()

           set r to new DocRun(p::"StandardOutput"::"ReadToEnd"())
           invoke p::"WaitForExit"()
           invoke outputParagraph::"Inlines"::"Add"(r)

           invoke p::"Dispose"()
       end method RunBatchFile.

       method-id.  RunExe.
       working-storage section.
       01  p  type "System.Diagnostics.Process".
       01  r  DocRun.
       procedure division using exeFile as string.
           if type "System.IO.File"::"Exists"(exeFile)
               set p to new type "System.Diagnostics.Process"
               set p::"StartInfo"::"UseShellExecute" to false
               set p::"StartInfo"::"RedirectStandardInput" to true
               set p::"StartInfo"::"RedirectStandardError" to true
               set p::"StartInfo"::"FileName" to exeFile
               invoke p::"Start"()

               set r to new DocRun(p::"StandardOutput"::"ReadToEnd"())
               invoke p::"WaitForExit"()
               invoke outputParagraph::"Inlines"::"Add"(r)

               invoke p::"Dispose"()
           end-if
       end method RunExe.

       method-id  DeleteTempFiles.
       working-storage section.
       01  pattern  string.
       01  paths    ArrayOfString.
       01  p        string.
       01  i        binary-long.
       procedure division using srcFile as string.
           set pattern to Path::"GetFileNameWithoutExtension"(srcFile)
           set pattern to string::"Concat"(pattern, ".*")
           
           set i to 0
           set paths to Directory::"GetFiles"(".", pattern)
           perform with test before until i = paths::"Length"
               set p to paths::"GetValue"(i) as string
               invoke type "System.IO.File"::"Delete"(p)
               add 1 to i
           end-perform
       end method DeleteTempFiles.

       method-id. Export final private.
       working-storage section.
       01  dialog        SaveFileDialog.
       01  dialogResult  type "System.Nullable"[condition-value].
       01  snip          Snippet.
       procedure division using by value sender as object e as type "System.Windows.RoutedEventArgs".
           set dialog to new SaveFileDialog
           set dialog::"AddExtension" to true
           set dialog::"DefaultExt" to self::"DefaultExportExt"
           set dialog::"Filter" to self::"ExportFilter"

           set dialogResult to dialog::"ShowDialog"()

           if dialogResult::"HasValue" and dialogResult::"Value" then
               if self::"goButton1"::"IsEnabled"
                   set snip to Snippet::"CreateNew"(self::"_activeLanguage", codeBox::"Text", self::"CurrentOptions")
               else
                   set snip to Snippet::"CreateNew"(self::"_activeLanguage", codeBox2::"Text", self::"CurrentOptions")
               end-if
               invoke snip::"Export"(dialog::"FileName")
           end-if
       end method Export.

       method-id. RemoveAnnotations final private.
       procedure division using by value sender as object e as type "System.Windows.RoutedEventArgs".
           if self::"goButton1"::"IsEnabled"
               set codeBox::"Text" to Snippet::"RemoveAnnotations"(codeBox::"Text")
           else
               set codeBox2::"Text" to Snippet::"RemoveAnnotations"(codeBox2::"Text")
           end-if
       end method RemoveAnnotations.

       method-id.  "About" final private.
       working-storage section.
       01  about  AboutWindow value new AboutWindow.
       procedure division using by value sender as object e as type "System.Windows.Input.ExecutedRoutedEventArgs".
           invoke about::"ShowDialog"()
       end method "About".
       
       method-id.  "IssueTracker" final private.
       procedure division using by value sender as object e as type "System.Windows.Input.ExecutedRoutedEventArgs".
           invoke type "System.Diagnostics.Process"::"Start"("http://www.codeplex.com/barbarian/WorkItem/List.aspx")
       end method "IssueTracker".

       method-id.  "LearnCOBOL" final private.
       procedure division using by value sender as object e as type "System.Windows.Input.ExecutedRoutedEventArgs".
           invoke type "System.Diagnostics.Process"::"Start"("http://cobol.tech.officelive.com/default.aspx")
       end method "LearnCOBOL".

       method-id.  "Window_SizeChanged" final private.
       procedure division using by value sender as object e as type "System.Windows.SizeChangedEventArgs".
           invoke self::"ResizeControls"()
       end method "Window_SizeChanged".

       method-id.  "ResizeControls" final private.
       procedure division.
           set self::"mainDockPanel"::"LastChildFill" to false

           if self::"codeGroupBox2" = null 
           or self::"codeGroupBox2"::"Visibility" = Visibility::"Visible"
*              We have 2 code input boxes. Null just means the XAML is still loading.
               if self::"WindowState" = WindowState::"Maximized"
                   set self::"codeDockPanel"::"Width" to 
                       SystemInformation::"PrimaryMonitorMaximizedWindowSize"::"Width" / 2
                       - self::"BorderThickness"
               else
                   set self::"codeDockPanel"::"Width" to (self::"Width" / 2) - self::"BorderThickness"
               end-if
            else
*              We only have 1 visible code input box. Let it grow.
               if self::"WindowState" = WindowState::"Maximized"
                   set self::"codeDockPanel"::"Width" to 
* Todo: Take the time to do the XAML arithmetic here, instead of hard-coding.
                       SystemInformation::"PrimaryMonitorMaximizedWindowSize"::"Width" - 134
               else
* Todo: Ditto.
                   set self::"codeDockPanel"::"Width" to self::"Width" - 134
               end-if
           end-if

           set self::"mainDockPanel"::"LastChildFill" to true
       end method "ResizeControls".
       
       method-id.  "codeBox_GotFocus" final private.
       working-storage section.
       01  languageItem   ComboBoxItem.
       procedure division using by value sender as object e as type "System.Windows.RoutedEventArgs".
           set self::"goButton1"::"IsEnabled" to true
           set self::"goButton2"::"IsEnabled" to false
           set languageItem to languageComboBox::"SelectedItem" as ComboBoxItem
           set self::"_activeLanguage" to languageItem::"Content"::"ToString"()
       end method "codeBox_GotFocus".

       method-id.  "codeBox2_GotFocus" final private.
       working-storage section.
       01  languageItem   ComboBoxItem.
       procedure division using by value sender as object e as type "System.Windows.RoutedEventArgs".
           set self::"goButton1"::"IsEnabled" to false
           set self::"goButton2"::"IsEnabled" to true
           set languageItem to languageComboBox2::"SelectedItem" as ComboBoxItem
           set self::"_activeLanguage" to languageItem::"Content"::"ToString"()
       end method "codeBox2_GotFocus".
       
       method-id.  "languageComboBox_GotFocus" final private.
       procedure division using by value sender as object e as type "System.Windows.RoutedEventArgs".
           set self::"goButton1"::"IsEnabled" to true
           set self::"goButton2"::"IsEnabled" to false
       end method "languageComboBox_GotFocus".

       method-id.  "languageComboBox2_GotFocus" final private.
       procedure division using by value sender as object e as type "System.Windows.RoutedEventArgs".
           if self::"codeGroupBox2"::"Visibility" = Visibility::"Visible"
               set self::"goButton1"::"IsEnabled" to false
               set self::"goButton2"::"IsEnabled" to true
           else
               set self::"goButton1"::"IsEnabled" to true
               set self::"goButton2"::"IsEnabled" to false
           end-if
       end method "languageComboBox2_GotFocus".

       method-id.  "languageComboBox_SelectionChanged" final private.
       working-storage section.
       01  languageItem ComboBoxItem.
       procedure division using by value sender as object e as type "System.EventArgs".
           set languageItem to languageComboBox::"SelectedItem" as ComboBoxItem
           set self::"_activeLanguage" to languageItem::"Content"::"ToString"()
       end method "languageComboBox_SelectionChanged".

       method-id.  "languageComboBox2_SelectionChanged" final private.
       working-storage section.
       01  languageItem ComboBoxItem.
       procedure division using by value sender as object e as type "System.EventArgs".
*          In WPF, this is how we get the item to which the selection is changing.
*          The SelectedItem property returns the _previously_ selected item.
           set languageItem to languageComboBox2::"Items"::"GetItemAt"(
               languageComboBox2::"SelectedIndex") as ComboBoxItem

           if languageItem::"Content"::"ToString"() = string::"Empty"
*              Hide language 2, and make language 1 active
               set self::"_activeLanguage" to languageItem::"Content"::"ToString"()
               set self::"codeGroupBox2"::"Visibility" to Visibility::"Collapsed"
           else
*              Make language 2 active
               set self::"_activeLanguage" to languageItem::"Content"::"ToString"()
               if not self::"codeGroupBox2" = null
*                  We have to test for null, because this event fires on app startup,
*                  before codeGroupBox2 has been created.
                   set self::"codeGroupBox2"::"Visibility" to Visibility::"Visible"
               end-if
           end-if

           invoke self::"ResizeControls"()
       end method "languageComboBox2_SelectionChanged".

       end object.
       end class MainWindow.
